<?php

namespace App\Logic\Payment;

use App\Constants\Code;
use App\Exception\LogicException;
use App\Logic\AbstractLogic;
use App\Model\Payment\PayRecord;
use GuzzleHttp\Psr7\Response;
use Hyperf\Contract\ContainerInterface;
use Yansongda\HyperfPay\Pay;
use Yansongda\Supports\Collection;
use function Hyperf\Config\config;

class PayLogic extends AbstractLogic
{
    /**
     * @var $payer Pay
     */
    protected $payer;
    public $order_sn;
    public $amount;
    public $subject;

    public function __construct(ContainerInterface $container,Pay $payer)
    {
        parent::__construct($container);
        $this->payer = $payer;
    }

    public function rules(): array
    {
        return [
            "order_sn" => "required|string|max:64",
            "amount" => "required|integer|min:1|max:4294967295",
            "subject" => "required|string|max:255",
        ];
    }

    /**
     * 获取支付记录
     * 1：先看order_sn是否存在，如果存在要判断状态，已经完成支付的就无法再次发起
     * 2：如果找到未支付的记录，先去接口查询支付结果：如果未支付就取消支付
     * 3：如果没有找到支付记录就生成一条
     * @return PayRecord
     */
    protected function record(string $method) :PayRecord
    {
        /** @var $record PayRecord **/
        $record = PayRecord::where("order_sn",$this->order_sn)->orderBy("id","desc")->first();
        //已经存在支付单
        if($record) {
            //如果已经支付过了
            if($record->state == PayRecord::STATE_PAID){
                throw new LogicException(Code::ORDER_ALREADY_PAID);
            }
            //如果待支付要去查一下是否需要取消支付
            if($record->state == PayRecord::STATE_INIT){
                if (!$this->cancel($record)){
                    throw new LogicException(Code::ORDER_ALREADY_PAID);
                }
            }
        }
        return PayRecord::create([
            "order_sn" => $this->order_sn,
            "amount" => $this->amount,
            "subject" => $this->subject,
            "pay_method"=> $method,
            "state" => PayRecord::STATE_INIT,
            "create_time" => time(),
            "out_trade_no" => sprintf("%s-%s",$this->order_sn, uniqid())
        ]);
    }

    /**
     * 支付宝支付
     * @param string $method
     * @param array $options
     * @return array|\Psr\Http\Message\StreamInterface
     * @throws LogicException
     */
    public function alipay(string $method,array $options = []) :mixed
    {
        $alipay = $this->payer->alipay();
        if(!in_array($method,["mp","wap","app","pos","scan","mini"])){
            throw new LogicException(Code::INVALID_PAY_METHOD);
        }
        try{
            $record = $this->record(PayRecord::PAYMENT_ALIPAY);
            $params = [
                "out_trade_no" => $record->out_trade_no,
                "total_amount" => $record->amount,
                "subject" => $record->subject,
            ];
            $result = call_user_func_array([$alipay,$method],$params + $options);
            return $this->payload($result);
        }catch (\Throwable $e) {
            $this->logger->error(sprintf("支付宝发起失败：%s",$e->getMessage()));
            throw new LogicException(Code::PAY_ERROR_CONFIG);
        }
    }

    /**
     * 微信支付参数获取
     * @param string $method
     * @param array $options
     * @return array
     * @throws LogicException
     */
    public function wechat(string $method,array $options = []) :mixed
    {
        $wechat = $this->payer->wechat();
        if(!in_array($method,["mp","wap","app","pos","scan","mini"])){
            throw new LogicException(Code::INVALID_PAY_METHOD);
        }
        try{
            $record = $this->record(PayRecord::PAYMENT_WECHAT);
            $params = [
                "out_trade_no" => $record->out_trade_no,
                "amount" => ["total"=>$record->amount],
                "description" => $record->subject,
            ];
            $result = call_user_func_array([$wechat,$method],$params + $options);
            return $this->payload($result);
        }catch (\Throwable $e) {
            $this->logger->error(sprintf("微信支付发起失败：%s",$e->getMessage()));
            throw new LogicException(Code::PAY_ERROR_CONFIG);
        }
    }

    /**
     * 银联支付
     * @param string $method
     * @param array $options
     * @return array
     * @throws LogicException
     */
    public function unipay(string $method,array $options = []) :mixed
    {
        $unipay = $this->payer->unipay();
        if(!in_array($method,["wap","web","pos","scan"])){
            throw new LogicException(Code::INVALID_PAY_METHOD);
        }
        try{
            $record = $this->record(PayRecord::PAYMENT_UNIPAY);
            $params = [
                "orderId" => $record->out_trade_no,
                "txnAmt" => $record->amount,
                'txnTime' => date('YmdHis'),
            ];
            $result = call_user_func_array([$unipay,$method],$params + $options);
            return $this->payload($result);
        }catch (\Throwable $e) {
            $this->logger->error(sprintf("银联支付发起失败：%s",$e->getMessage()));
            throw new LogicException(Code::PAY_ERROR_CONFIG);
        }
    }

    /**
     * 取消订单支付请求，如果不是已经支付的必定取消成功
     * @param PayRecord $record 支付请求记录
     * @return bool 取消结果
     */
    public function cancel(PayRecord $record) :bool
    {
        try{
            switch ($record->pay_method)
            {
                case PayRecord::PAYMENT_ALIPAY:
                    $this->payer->alipay()->cancel(['out_trade_no' => $record->out_trade_no]);
                    //TODO 取消成功判断
                    break;
                case PayRecord::PAYMENT_WECHAT:
                    $this->payer->wechat()->close(['out_trade_no' => $record->out_trade_no]);
                    //TODO 取消成功判断
                    break;
                case PayRecord::PAYMENT_UNIPAY:
                    $this->payer->unipay()->cancel([
                        //TODO 取消参数及判断
                    ]);
                    break;
            }
        }catch (\Throwable $t){
            $this->logger->error(sprintf("取消支付[%s]失败：%s",$record->out_trade_no,$t->getMessage()));
        }
        return true;
    }

    /**
     * 对结果进行转化
     * @param $result
     * @return array|\Psr\Http\Message\StreamInterface
     */
    protected function payload($result)
    {
        if($result instanceof Collection) {
            return $result->toArray();
        }
        else if($result instanceof Response){
            return $result->getBody();
        }
        return $result;
    }
}